package main

import (
	"bufio"
	"context"
	"database/sql"
	"fmt"
	"gfast/app/common/global"
	"gfast/app/common/service"
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/errors/gerror"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/os/gfile"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/os/gview"
	"github.com/gogf/gf/text/gregex"
	"github.com/gogf/gf/text/gstr"
	"github.com/gogf/gf/util/gconv"
	"io"
	"os"
	"regexp"
	"strings"
)

type temp struct {
	Path string
	Tag  string
}

type generator struct {
	Mapping map[string]temp
	View    *gview.View
}

var Gen = &generator{
	Mapping: map[string]temp{
		"dao":          {Path: "go/dao.template"},
		"dao_internal": {Path: "go/dao_internal.template"},
		"model":        {Path: "go/model.template"},
		"controller":   {Path: "go/controller.template"},
		"service":      {Path: "go/service.template"},
		"router":       {Path: "go/router.template"},
		"sql-register": {Path: "sql/register.template"},
		"sql-create":   {Path: "sql/create.template"},
		"jsApi":        {Path: "js/api.template"},
		"vue":          {Path: "vue/list-vue.template", Tag: "crud"},
		"vue-tree":     {Path: "vue/tree-vue.template", Tag: "tree"},
	},
	View: func() *gview.View {
		view := gview.New()
		_ = view.SetConfigWithMap(g.Map{
			"Paths":      g.Cfg().GetString("gen.templatePath"),
			"Delimiters": []string{"{{", "}}"},
		})
		view.BindFuncMap(g.Map{
			"UcFirst": func(str string) string {
				return gstr.UcFirst(str)
			},
			"Sum": func(a, b int) int {
				return a + b
			},
			"CaseCamelLower": gstr.CaseCamelLower, //首字母小写驼峰
			"CaseCamel":      gstr.CaseCamel,      //首字母大写驼峰
			"HasSuffix":      gstr.HasSuffix,      //是否存在后缀
			"ContainsI":      gstr.ContainsI,      //是否包含子字符串
			"VueTag": func(t string) string {
				return t
			},
			"subtract": func(v1, v2 int) int {
				return v1 - v2
			},
			"JsExpr": func(prefix string, expr string) string {
				reg, _ := regexp.Compile("__([a-zA-Z_-]*)__")
				return reg.ReplaceAllStringFunc(expr, func(s string) string {
					ss := reg.FindStringSubmatch(s)
					return prefix + gstr.CaseCamelLower(ss[1])
				})
			},
		})
		return view
	}(),
}

func (s *generator) Write(tables []*Table, ctx context.Context) (err error) {
	//获取当前运行时目录
	curDir, err := os.Getwd()
	if err != nil {
		return gerror.New("获取本地路径失败")
	}
	frontDir := g.Cfg().GetString("gen.frontDir")
	if !gfile.IsDir(frontDir) {
		err = gerror.New("项目前端路径不存在，请检查是否已在配置文件中配置！")
		return
	}
	for _, table := range tables {
		var codes g.MapStrStr
		codes, err = s.Gen(table, ctx)
		if err != nil {
			return err
		}
		packageName := gstr.SubStr(table.PackageName, gstr.Pos(table.PackageName, "/"))
		businessName := gstr.CaseCamelLower(table.BusinessName)
		for key, code := range codes {
			switch key {
			case "controller":
				path := strings.Join([]string{curDir, packageName, "/api/", table.TableName, ".go"}, "")
				err = s.createFile(path, code, false)
			case "dao":
				path := strings.Join([]string{curDir, packageName, "/dao/", table.TableName, ".go"}, "")
				err = s.createFile(path, code, false)
			case "dao_internal":
				path := strings.Join([]string{curDir, packageName, "/dao/internal/", table.TableName, ".go"}, "")
				err = s.createFile(path, code, true)
			case "model":
				path := strings.Join([]string{curDir, packageName, "/model/", table.TableName, ".go"}, "")
				err = s.createFile(path, code, true)
			case "router":
				path := strings.Join([]string{curDir, packageName, "/router/", table.TableName, ".go"}, "")
				err = s.createFile(path, code, false)
			case "service":
				path := strings.Join([]string{curDir, packageName, "/service/", table.TableName, ".go"}, "")
				err = s.createFile(path, code, false)
			case "sql-create":
				path := strings.Join([]string{curDir, "/data/gen_sql_create/", packageName, "/", table.TableName, ".sql"}, "")
				hasSql := gfile.Exists(path)
				err = s.createFile(path, code, false)
				if !hasSql {
					//第一次生成则向数据库写入菜单数据
					err = s.writeDb(path, ctx)
					if err != nil {
						return
					}
				}
			case "sql-register":
				path := strings.Join([]string{curDir, "/data/gen_sql_register/", packageName, "/", table.TableName, ".sql"}, "")
				hasSql := gfile.Exists(path)
				err = s.createFile(path, code, false)
				if !hasSql {
					//第一次生成则向数据库写入菜单数据
					err = s.writeDb(path, ctx)
					if err != nil {
						return
					}
					//清除菜单缓存
					service.Cache.New().Remove(global.SysAuthMenu)
				}
			case "vue", "vue-tree":
				path := strings.Join([]string{frontDir, "/src/views/", table.ModuleName, "/", businessName, "/list/index.vue"}, "")
				if gstr.ContainsI(table.PackageName, "plugins") {
					path = strings.Join([]string{frontDir, "/src/views/plugins/", table.ModuleName, "/", businessName, "/list/index.vue"}, "")
				}
				err = s.createFile(path, code, false)
			case "jsApi":
				path := strings.Join([]string{frontDir, "/src/api/", table.ModuleName, "/", businessName, ".js"}, "")
				if gstr.ContainsI(table.PackageName, "plugins") {
					path = strings.Join([]string{frontDir, "/src/api/plugins/", table.ModuleName, "/", businessName, ".js"}, "")
				}
				err = s.createFile(path, code, false)
			}
		}

		codes, err = s.GenActions(table, ctx)
		if err != nil {
			return err
		}
		for name, code := range codes {
			path := strings.Join([]string{frontDir, "/src/views/", table.ModuleName, "/", businessName, "/dialog/" + name + ".vue"}, "")
			err = s.createFile(path, code, false)
		}

		//生成对应的模块路由
		err = s.genModuleRouter(curDir, table.ModuleName, table.PackageName)
	}
	return
}

func (s *generator) Gen(table *Table, ctx context.Context) (data g.MapStrStr, err error) {
	table.CreateTime = gtime.Now()
	params := g.Map{"table": table}
	data = g.MapStrStr{}
	for key, conf := range s.Mapping {
		if conf.Tag != "" && conf.Tag != table.TplCategory {
			continue
		}
		var res string
		if res, err = s.View.Parse(ctx, conf.Path, params); err == nil {
			res, err = s.trimBreak(res)
			data[key] = res
		} else {
			return
		}
	}
	return
}

func (s *generator) GenActions(table *Table, ctx context.Context) (data g.MapStrStr, err error) {
	data = g.MapStrStr{}
	for _, action := range table.Actions {
		action.Table.CreateTime = gtime.Now()
		var res string
		if res, err = s.View.Parse(ctx, "vue/dialog-vue.template", g.Map{
			"table":  action.Table,
			"action": action,
		}); err == nil {
			res, err = s.trimBreak(res)
			data[action.Name] = res
		} else {
			return
		}
	}
	return
}

// 剔除多余的换行
func (s *generator) trimBreak(str string) (rStr string, err error) {
	var b []byte
	if b, err = gregex.Replace("(([\\s\t]*)\r?\n){2,}", []byte("$2\n"), []byte(str)); err != nil {
		return
	}
	if b, err = gregex.Replace("(([\\s\t]*)/{4}\r?\n)", []byte("$2\n\n"), b); err == nil {
		rStr = gconv.String(b)
	}
	return
}

// createFile 创建文件
func (s *generator) createFile(fileName, data string, cover bool) (err error) {
	create := func(path string) (*os.File, error) {
		dir := gfile.Dir(path)
		if !gfile.Exists(dir) {
			if err := gfile.Mkdir(dir); err != nil {
				return nil, err
			}
		}
		return os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
	}
	if !gfile.Exists(fileName) || cover {
		var f *os.File
		f, err = create(fileName)
		if err == nil {
			f.WriteString(data)
		}
		f.Close()
	}
	return
}

// 写入菜单数据
func (s *generator) writeDb(path string, ctx context.Context) (err error) {
	isAnnotation := false
	var fi *os.File
	fi, err = os.Open(path)
	if err != nil {
		return
	}
	defer fi.Close()
	br := bufio.NewReader(fi)
	var sqlStr []string
	now := gtime.Now()
	var res sql.Result
	var id int64
	var tx *gdb.TX
	tx, err = g.DB().Ctx(ctx).Begin()
	if err != nil {
		return
	}
	for {
		bytes, e := br.ReadBytes('\n')
		if e == io.EOF {
			break
		}
		str := gstr.Trim(string(bytes))

		if str == "" {
			continue
		}

		if strings.Contains(str, "/*") {
			isAnnotation = true
		}

		if isAnnotation {
			if strings.Contains(str, "*/") {
				isAnnotation = false
			}
			continue
		}

		if str == "" || strings.HasPrefix(str, "--") || strings.HasPrefix(str, "#") {
			continue
		}
		if strings.HasSuffix(str, ";") {
			if gstr.ContainsI(str, "select") {
				if gstr.ContainsI(str, "@now") {
					continue
				}
				if gstr.ContainsI(str, "@parentId") {
					id, err = res.LastInsertId()
				}
			}
			sqlStr = append(sqlStr, str)
			sql := strings.Join(sqlStr, "")
			gstr.ReplaceByArray(sql, []string{"@parentId", gconv.String(id), "@now", now.Format("Y-m-d H:i:s")})
			//插入业务
			res, err = tx.Exec(sql)
			if err != nil {
				tx.Rollback()
				return
			}
			sqlStr = nil
		} else {
			sqlStr = []string{str}
		}
	}
	tx.Commit()
	return
}

// GenModuleRouter 生成模块路由
func (s *generator) genModuleRouter(curDir, moduleName, packageName string) (err error) {
	if gstr.CaseSnake(moduleName) != "system" {
		routerFilePath := strings.Join([]string{curDir, "/router/", gstr.CaseSnake(moduleName), ".go"}, "")
		if gstr.ContainsI(packageName, "plugins") {
			routerFilePath = strings.Join([]string{curDir, "/plugins/router/", gstr.CaseSnake(moduleName), ".go"}, "")
		}
		code := fmt.Sprintf(`package router%simport _ "%s/router"`, "\n", packageName)
		err = s.createFile(routerFilePath, code, false)
	}
	return
}
